package com.unknownman.block.transaction.subscribe;

import com.unknownman.block.component.MessageSolver;
import com.unknownman.block.component.MessageSolverChooser;
import com.unknownman.block.constants.ChainContractConstants;
import com.unknownman.block.constants.ChainContractConstants.ContractTypeConstants;
import com.unknownman.block.constants.LogConstants;
import com.unknownman.block.dto.ChainContractComposition;
import com.unknownman.block.entity.TContractConfig;
import com.unknownman.block.entity.TContractLogs;
import com.unknownman.block.service.TChainConfigService;
import com.unknownman.block.service.TContractConfigService;
import com.unknownman.block.service.TContractLogsService;
import lombok.extern.slf4j.Slf4j;
import org.cloud.utils.CollectionUtil;
import org.cloud.utils.SpringContextUtil;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.web3j.contracts.eip20.generated.ERC20;
import org.web3j.protocol.Web3j;
import org.web3j.protocol.core.DefaultBlockParameterNumber;
import org.web3j.protocol.core.methods.response.EthBlock;
import org.web3j.protocol.core.methods.response.Log;
import org.web3j.protocol.core.methods.response.TransactionReceipt;
import org.web3j.protocol.http.HttpService;
import org.web3j.tx.ReadonlyTransactionManager;
import org.web3j.tx.gas.DefaultGasProvider;

import java.math.BigInteger;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.function.Function;
import java.util.stream.Collectors;

@Slf4j
public class EthTranFilter2 extends BaseTranFilter {

    private ExecutorService receiveExecutor = Executors.newFixedThreadPool(10);

    private boolean stop = false;
    private long checkInterval = 3000;

    private final MessageSolverChooser messageSolverChooser;

    private final TChainConfigService chainConfigService;
    private final TContractLogsService contractLogsService;
    private final TContractConfigService contractConfigService;

    private final ChainContractComposition chainContractComposition;

    private final Map<String, TContractConfig> contractConfigMap;

    public EthTranFilter2(ChainContractComposition chainContractComposition) {
        super();

        chainConfigService = SpringContextUtil.getBean(TChainConfigService.class);
        contractLogsService = SpringContextUtil.getBean(TContractLogsService.class);
        messageSolverChooser = SpringContextUtil.getBean(MessageSolverChooser.class);
        contractConfigService = SpringContextUtil.getBean(TContractConfigService.class);
        this.chainContractComposition = chainContractComposition;
        //查出启用的合约地址
        contractConfigMap = this.chainContractComposition.getContractList().stream()
            .filter(cont -> cont.getContractStatus() == ChainContractConstants.ContractStatusConstants.NORMAL.getStatus())
            .collect(Collectors.toMap((contractConfig) -> contractConfig.getContractAddress().toUpperCase(), Function.identity()));

        this.createWe3j();
    }

    /**
     * 添加监听
     */
    @Override
    public boolean addFilter(BigInteger startBlock) {
        lastBlock = startBlock;
        stop = false;
        log.info("addFilter2: 开始扫描区块获取事件, [startBlock: {}]", startBlock);
        final long[] nextCheck = {0};
        new Thread(() -> {
            while (!(Thread.interrupted() || stop)) {
                if (nextCheck[0] <= System.currentTimeMillis()) {
                    nextCheck[0] = System.currentTimeMillis() + checkInterval;
                    try {
                        runScan();
                    } catch (Exception e) {
                        stop = true;
                        e.printStackTrace();
                    }
                } else {
                    try {
                        Thread.sleep(Math.max(nextCheck[0] - System.currentTimeMillis(), 100));
                    } catch (InterruptedException ex) {
                        log.error("addFilter2: 扫描区块获取事件异常, [lastBlock: {}, e: {}]", lastBlock, ex.getMessage());
                        break;
                    }
                }
            }
            receiveExecutor.shutdown();
            log.warn("addFilter2: 结束扫描区块获取事件, [lastBlock: {}]", lastBlock);
        }).start();
        return true;
    }


    private void runScan() throws Exception {
        long currentBlockNumber = lastBlock.longValue();
        // 当前最新区块高度
        BigInteger latest = web3j.ethBlockNumber().send().getBlockNumber();
        for (long i = currentBlockNumber + 1; i < latest.intValue() - 10; i++) {
            final long blockNum = i;
            // 超过30块就跳出，等待下次处理
            if (currentBlockNumber - lastBlock.longValue() >= 10) {
                break;
            }
            try {
                EthBlock.Block block = web3j.ethGetBlockByNumber(new DefaultBlockParameterNumber(blockNum), false).send().getBlock();
                if (block == null) {
                    return;
                }
                tempBlockTimestamp(block);
                for (EthBlock.TransactionResult<EthBlock.TransactionHash> transaction : block.getTransactions()) {
                    final String hash = String.valueOf(transaction.get());
                    receiveExecutor.execute(() -> {
                        try {
                            TransactionReceipt transactionReceipt = web3j.ethGetTransactionReceipt(hash).send().getTransactionReceipt()
                                .get();
                            subscribeCallBack(transactionReceipt);
                        } catch (Exception e) {
                            stop = true;
                            updateLastBlock(BigInteger.valueOf(blockNum - 1), false);
                            redisUtil.getRedisTemplate().opsForList().leftPush("runScan:errorHash", hash);
                            e.printStackTrace();
                        }
                    });
                }
                if (blockNum > currentBlockNumber) {
                    updateLastBlock(lastBlock, true);
                }
            } catch (Exception e) {
                e.printStackTrace();
                stop = true;
                updateLastBlock(BigInteger.valueOf(blockNum - 1), false);
            }
            lastBlock = BigInteger.valueOf(blockNum);
        }
    }

    private Map<Long, Long> blockTimestamp = new HashMap<>();

    public void tempBlockTimestamp(EthBlock.Block block) {
        blockTimestamp.remove(block.getNumber().longValue() - 100);
        blockTimestamp.put(block.getNumber().longValue(), block.getTimestamp().longValue());
    }

    @Override
    public void subscribeCallBack(TransactionReceipt transactionReceipt) throws Exception {

        if (web3j == null) {
            return;
        }

        List<Log> logs = transactionReceipt.getLogs();
        if (CollectionUtil.single().isEmpty(logs)) {
            return;
        }
//        EthBlock.Block block = web3j.ethGetBlockByHash(transactionReceipt.getBlockHash(), false).send().getBlock();
        long timestamp = blockTimestamp.get(transactionReceipt.getBlockNumber()) == null ? System.currentTimeMillis()
            : blockTimestamp.get(transactionReceipt.getBlockNumber()) * 1000;
        ArrayList<TContractLogs> saveLogList = new ArrayList<>();
        for (Log logChain : logs) {
            //判断是不是监听的日志
            TContractConfig contractConfig = contractConfigMap.get(logChain.getAddress().toUpperCase());
            if (contractConfig == null) {
                continue;
            }
            if (!contractLogsService.existsByHashAndIndex(transactionReceipt.getTransactionHash(), logChain.getLogIndex().longValue())) {
                TContractLogs contractLogs = new TContractLogs();
                contractLogs.setContractConfigId(contractConfig.getContractConfigId());
                contractLogs.setContractAddress(logChain.getAddress());
                contractLogs.setTransFrom(transactionReceipt.getFrom());
                if (!StringUtils.isEmpty(transactionReceipt.getContractAddress())) {
                    contractLogs.setTransTo(transactionReceipt.getContractAddress());
                } else {
                    contractLogs.setTransTo(transactionReceipt.getTo());
                }
                contractLogs.setTransHash(transactionReceipt.getTransactionHash());
                contractLogs.setIndexForBlock(logChain.getLogIndex().longValue());
                contractLogs.setBlockTimestamp(new Date(timestamp));
                contractLogs.setRpcAddress(chainContractComposition.getRpcAddress());
                List<String> topics = logChain.getTopics();
                contractLogs.setFirstTopic(topics.get(0));
                if (topics.size() >= 2) {
                    contractLogs.setSecondTopic(topics.get(1));
                }
                if (topics.size() >= 3) {
                    contractLogs.setThirdTopic(topics.get(2));
                }
                if (topics.size() >= 4) {
                    contractLogs.setFourthTopic(topics.get(3));
                }
                contractLogs.setLogData(logChain.getData());
                contractLogs.setStatus(LogConstants.ContractLogStatus.UNTREATED.getStatus());
                saveLogList.add(contractLogs);
                //消息发送
                //根据合约分发消息
                MessageSolver messageSolver = messageSolverChooser.choose(logChain.getAddress().toUpperCase());
                if (messageSolver != null) {
                    messageSolver.solve(logChain);
                }
            }
        }
        if (CollectionUtil.single().isNotEmpty(saveLogList)) {
            boolean b = contractLogsService.saveBatch(saveLogList);
            Assert.isTrue(b, "subscribeCallBack-" + chainContractComposition.getChainSymbol() + "：log保存失败");
        }

    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateLastBlock(BigInteger block, boolean success) {
        chainConfigService.updateLastBlock(chainContractComposition.getChainConfigId(), block.longValue(), success);
    }

    private void createWe3j() {
        final String[] bscRpcList = {
            "https://bsc-dataseed.binance.org",
            "wss://bsc.getblock.io/mainnet/?api_key=d80871fd-baa8-493a-a219-dcbffe3b772a",
            "https://bsc.getblock.io/mainnet/?api_key=d80871fd-baa8-493a-a219-dcbffe3b772a",
            "wss://bsc.getblock.io/mainnet/?api_key=d80871fd-baa8-493a-a219-dcbffe3b772a",
            "https://bsc.getblock.io/mainnet/?api_key=d80871fd-baa8-493a-a219-dcbffe3b772a",
            "wss://bsc.getblock.io/mainnet/?api_key=d80871fd-baa8-493a-a219-dcbffe3b772a",
            "https://bsc.getblock.io/mainnet/?api_key=d80871fd-baa8-493a-a219-dcbffe3b772a",
            "https://bsc-dataseed1.binance.org",
            "wss://bsc.getblock.io/mainnet/?api_key=d80871fd-baa8-493a-a219-dcbffe3b772a",
            "https://bsc.getblock.io/mainnet/?api_key=d80871fd-baa8-493a-a219-dcbffe3b772a",
            "wss://bsc.getblock.io/mainnet/?api_key=d80871fd-baa8-493a-a219-dcbffe3b772a",
            "https://bsc.getblock.io/mainnet/?api_key=d80871fd-baa8-493a-a219-dcbffe3b772a",
            "wss://bsc.getblock.io/mainnet/?api_key=d80871fd-baa8-493a-a219-dcbffe3b772a",
            "https://bsc.getblock.io/mainnet/?api_key=d80871fd-baa8-493a-a219-dcbffe3b772a",
            "https://bsc-dataseed2.binance.org",
            "wss://bsc.getblock.io/mainnet/?api_key=d80871fd-baa8-493a-a219-dcbffe3b772a",
            "https://bsc.getblock.io/mainnet/?api_key=d80871fd-baa8-493a-a219-dcbffe3b772a",
            "wss://bsc.getblock.io/mainnet/?api_key=d80871fd-baa8-493a-a219-dcbffe3b772a",
            "https://bsc.getblock.io/mainnet/?api_key=d80871fd-baa8-493a-a219-dcbffe3b772a",
            "wss://bsc.getblock.io/mainnet/?api_key=d80871fd-baa8-493a-a219-dcbffe3b772a",
            "https://bsc.getblock.io/mainnet/?api_key=d80871fd-baa8-493a-a219-dcbffe3b772a",
            "https://bsc-dataseed3.binance.org",
            "wss://bsc.getblock.io/mainnet/?api_key=d80871fd-baa8-493a-a219-dcbffe3b772a",
            "https://bsc.getblock.io/mainnet/?api_key=d80871fd-baa8-493a-a219-dcbffe3b772a",
            "wss://bsc.getblock.io/mainnet/?api_key=d80871fd-baa8-493a-a219-dcbffe3b772a",
            "https://bsc.getblock.io/mainnet/?api_key=d80871fd-baa8-493a-a219-dcbffe3b772a",
            "wss://bsc.getblock.io/mainnet/?api_key=d80871fd-baa8-493a-a219-dcbffe3b772a",
            "https://bsc.getblock.io/mainnet/?api_key=d80871fd-baa8-493a-a219-dcbffe3b772a",
            "https://bsc-dataseed4.binance.org",
            "wss://bsc.getblock.io/mainnet/?api_key=d80871fd-baa8-493a-a219-dcbffe3b772a",
            "https://bsc.getblock.io/mainnet/?api_key=d80871fd-baa8-493a-a219-dcbffe3b772a",
            "wss://bsc.getblock.io/mainnet/?api_key=d80871fd-baa8-493a-a219-dcbffe3b772a",
            "https://bsc.getblock.io/mainnet/?api_key=d80871fd-baa8-493a-a219-dcbffe3b772a",
            "wss://bsc.getblock.io/mainnet/?api_key=d80871fd-baa8-493a-a219-dcbffe3b772a",
            "https://bsc.getblock.io/mainnet/?api_key=d80871fd-baa8-493a-a219-dcbffe3b772a",
            "https://bsc-dataseed1.defibit.io",
            "wss://bsc.getblock.io/mainnet/?api_key=d80871fd-baa8-493a-a219-dcbffe3b772a",
            "https://bsc.getblock.io/mainnet/?api_key=d80871fd-baa8-493a-a219-dcbffe3b772a",
            "wss://bsc.getblock.io/mainnet/?api_key=d80871fd-baa8-493a-a219-dcbffe3b772a",
            "https://bsc.getblock.io/mainnet/?api_key=d80871fd-baa8-493a-a219-dcbffe3b772a",
            "wss://bsc.getblock.io/mainnet/?api_key=d80871fd-baa8-493a-a219-dcbffe3b772a",
            "https://bsc.getblock.io/mainnet/?api_key=d80871fd-baa8-493a-a219-dcbffe3b772a",
            "https://bsc-dataseed2.defibit.io",
            "wss://bsc.getblock.io/mainnet/?api_key=d80871fd-baa8-493a-a219-dcbffe3b772a",
            "https://bsc.getblock.io/mainnet/?api_key=d80871fd-baa8-493a-a219-dcbffe3b772a",
            "wss://bsc.getblock.io/mainnet/?api_key=d80871fd-baa8-493a-a219-dcbffe3b772a",
            "https://bsc.getblock.io/mainnet/?api_key=d80871fd-baa8-493a-a219-dcbffe3b772a",
            "wss://bsc.getblock.io/mainnet/?api_key=d80871fd-baa8-493a-a219-dcbffe3b772a",
            "https://bsc.getblock.io/mainnet/?api_key=d80871fd-baa8-493a-a219-dcbffe3b772a",
            "https://bsc-dataseed3.defibit.io",
            "wss://bsc.getblock.io/mainnet/?api_key=d80871fd-baa8-493a-a219-dcbffe3b772a",
            "https://bsc.getblock.io/mainnet/?api_key=d80871fd-baa8-493a-a219-dcbffe3b772a",
            "wss://bsc.getblock.io/mainnet/?api_key=d80871fd-baa8-493a-a219-dcbffe3b772a",
            "https://bsc.getblock.io/mainnet/?api_key=d80871fd-baa8-493a-a219-dcbffe3b772a",
            "wss://bsc.getblock.io/mainnet/?api_key=d80871fd-baa8-493a-a219-dcbffe3b772a",
            "https://bsc.getblock.io/mainnet/?api_key=d80871fd-baa8-493a-a219-dcbffe3b772a",
            "https://bsc-dataseed4.defibit.io",
            "wss://bsc.getblock.io/mainnet/?api_key=d80871fd-baa8-493a-a219-dcbffe3b772a",
            "https://bsc.getblock.io/mainnet/?api_key=d80871fd-baa8-493a-a219-dcbffe3b772a",
            "wss://bsc.getblock.io/mainnet/?api_key=d80871fd-baa8-493a-a219-dcbffe3b772a",
            "https://bsc.getblock.io/mainnet/?api_key=d80871fd-baa8-493a-a219-dcbffe3b772a",
            "wss://bsc.getblock.io/mainnet/?api_key=d80871fd-baa8-493a-a219-dcbffe3b772a",
            "https://bsc.getblock.io/mainnet/?api_key=d80871fd-baa8-493a-a219-dcbffe3b772a",
            "https://bsc-dataseed1.ninicoin.io",
            "wss://bsc.getblock.io/mainnet/?api_key=d80871fd-baa8-493a-a219-dcbffe3b772a",
            "https://bsc.getblock.io/mainnet/?api_key=d80871fd-baa8-493a-a219-dcbffe3b772a",
            "wss://bsc.getblock.io/mainnet/?api_key=d80871fd-baa8-493a-a219-dcbffe3b772a",
            "https://bsc.getblock.io/mainnet/?api_key=d80871fd-baa8-493a-a219-dcbffe3b772a",
            "wss://bsc.getblock.io/mainnet/?api_key=d80871fd-baa8-493a-a219-dcbffe3b772a",
            "https://bsc.getblock.io/mainnet/?api_key=d80871fd-baa8-493a-a219-dcbffe3b772a",
            "https://bsc-dataseed2.ninicoin.io",
            "wss://bsc.getblock.io/mainnet/?api_key=d80871fd-baa8-493a-a219-dcbffe3b772a",
            "https://bsc.getblock.io/mainnet/?api_key=d80871fd-baa8-493a-a219-dcbffe3b772a",
            "wss://bsc.getblock.io/mainnet/?api_key=d80871fd-baa8-493a-a219-dcbffe3b772a",
            "https://bsc.getblock.io/mainnet/?api_key=d80871fd-baa8-493a-a219-dcbffe3b772a",
            "wss://bsc.getblock.io/mainnet/?api_key=d80871fd-baa8-493a-a219-dcbffe3b772a",
            "https://bsc.getblock.io/mainnet/?api_key=d80871fd-baa8-493a-a219-dcbffe3b772a",
            "https://bsc-dataseed3.ninicoin.io",
            "wss://bsc.getblock.io/mainnet/?api_key=d80871fd-baa8-493a-a219-dcbffe3b772a",
            "https://bsc.getblock.io/mainnet/?api_key=d80871fd-baa8-493a-a219-dcbffe3b772a",
            "wss://bsc.getblock.io/mainnet/?api_key=d80871fd-baa8-493a-a219-dcbffe3b772a",
            "https://bsc.getblock.io/mainnet/?api_key=d80871fd-baa8-493a-a219-dcbffe3b772a",
            "wss://bsc.getblock.io/mainnet/?api_key=d80871fd-baa8-493a-a219-dcbffe3b772a",
            "https://bsc.getblock.io/mainnet/?api_key=d80871fd-baa8-493a-a219-dcbffe3b772a",
            "https://bsc-dataseed4.ninicoin.io",
            "wss://bsc.getblock.io/mainnet/?api_key=d80871fd-baa8-493a-a219-dcbffe3b772a",
            "https://bsc.getblock.io/mainnet/?api_key=d80871fd-baa8-493a-a219-dcbffe3b772a",
            "wss://bsc.getblock.io/mainnet/?api_key=d80871fd-baa8-493a-a219-dcbffe3b772a",
            "https://bsc.getblock.io/mainnet/?api_key=d80871fd-baa8-493a-a219-dcbffe3b772a",
            "wss://bsc.getblock.io/mainnet/?api_key=d80871fd-baa8-493a-a219-dcbffe3b772a",
            "https://bsc.getblock.io/mainnet/?api_key=d80871fd-baa8-493a-a219-dcbffe3b772a",
            "wss://bsc-ws-node.nariox.org",
            "wss://bsc.getblock.io/mainnet/?api_key=d80871fd-baa8-493a-a219-dcbffe3b772a",
            "https://bsc.getblock.io/mainnet/?api_key=d80871fd-baa8-493a-a219-dcbffe3b772a",
            "wss://bsc.getblock.io/mainnet/?api_key=d80871fd-baa8-493a-a219-dcbffe3b772a",
            "https://bsc.getblock.io/mainnet/?api_key=d80871fd-baa8-493a-a219-dcbffe3b772a",
            "wss://bsc.getblock.io/mainnet/?api_key=d80871fd-baa8-493a-a219-dcbffe3b772a",
            "https://bsc.getblock.io/mainnet/?api_key=d80871fd-baa8-493a-a219-dcbffe3b772a"
        };

        if (!ChainContractConstants.ChainSymbolEnum.BSC.getSymbol().equalsIgnoreCase(chainContractComposition.getChainSymbol())) {
            rpcAddress = chainContractComposition.getRpcAddress();
        } else {
            chainContractComposition.setRpcAddress(bscRpcList[new Random().nextInt(bscRpcList.length)]);
        }
        try {
            web3j = Web3j.build(new HttpService(chainContractComposition.getRpcAddress()));
        } catch (Exception e) {
            log.info("初始化w3[{}]异常，请检查:{}", chainContractComposition.getRpcAddress(), e.getMessage());
            return;
        }
        this.rpcAddress = chainContractComposition.getRpcAddress();
        log.info("初始化w3[{}]成功！", chainContractComposition.getRpcAddress());

        ReadonlyTransactionManager transactionManager = new ReadonlyTransactionManager(web3j, "0x0000000000000000000000000000000000000000");
        this.chainContractComposition.getContractList().forEach((contract) -> {
            if (ContractTypeConstants.ERC20.getType() == contract.getContractType() ||
                ContractTypeConstants.ERC721.getType() == contract.getContractType()
            ) {
                ERC20 erc20 = ERC20.load(contract.getContractAddress(), web3j, transactionManager, new DefaultGasProvider());
                try {
                    if (ContractTypeConstants.ERC721.getType() == contract.getContractType()) {
                        contract.setErc20Decimals(0);
                    } else {
                        contract.setTotalSupply(erc20.totalSupply().send());
                    }
                    contract.setUpdateBy("System");
                    contractConfigService.updateById(contract);
                } catch (Exception e) {
                    log.error("{}", e.getMessage());
                }
            }
        });
    }

    /**
     * 订阅是否关闭
     *
     * @return
     */
    @Override
    public boolean isDisposed() {
        return stop;
    }

    /**
     * 关闭订阅
     */
    @Override
    public void stopSubscribe() {
        stop = true;
    }

}
